#include <user.h>
#include <bwio.h>
#include <ts7200.h>
#include <defs.h>
#include <vic.h>
#include <uart.h>
#include <iobuf.h>
#include <isr.h>
#include <train.h>

int TrainRxInit() {
  uart *const uart1 = (uart*)( UART1_BASE );
  
  // clear junk from receive FIFO
  int c;
  while (!(uart1->flag & RXFE_MASK)) {
    c = uart1->data;
  }

  // set up receive
  uart1->ctrl |= RIEN_MASK;
  return SUCCESS;
}

void TrainRxNotifier() {
  int server_tid;
  
  uart *const uart1 = (uart*)( UART1_BASE );
  
  iobuf buf;
  IobufInit(&buf);
  
  rx_reply msg;
  
  Receive(&server_tid, NULL, 0);
  Reply(server_tid, NULL, 0);

  for ( ; ; ) {
    AwaitEvent(EVENT_UART1_RX, NULL, 0);

    msg.ch = uart1->data;
    Send( server_tid, (char*)&msg, sizeof(rx_reply), NULL, 0 );
  }
}

void TrainRxServer() {
  TrainRxInit();
  
  int i_min;
  iobuf buf;
  IobufInit(&buf);

  // Getc() queue
  iobuf request_queue;
  IobufInit(&request_queue);
  
  rx_reply msg;  
  rx_reply reply;

  int notifier_tid = Create( MAX_PRI-1, &TrainRxNotifier );
  Send(notifier_tid, NULL, 0, NULL, 0);
  RegisterAs("train-rx-server");

  int sender_tid;
  uart *const uart1 = (uart*)( UART1_BASE );
  for ( ; ; ) {
    Receive(&sender_tid, (char*)&msg, sizeof(rx_reply));
    if (sender_tid == notifier_tid) {
      // notifier has data for me
      IobufEnqueue( &buf, msg.ch );
      Reply(notifier_tid, NULL, 0);
      
      if (!IobufEmpty(&request_queue)) {
        IobufDequeue(&request_queue, &i_min);
        IobufDequeue(&buf, &(reply.ch));
        Reply( i_min, (char*)&reply, sizeof(rx_reply) );
      }
    } else {
      IobufEnqueue(&request_queue, sender_tid);
      if (!IobufEmpty(&buf)) {
        IobufDequeue(&request_queue, &i_min);
        IobufDequeue(&buf, &(reply.ch));
        Reply( i_min, (char*)&reply, sizeof(rx_reply) );
      }
    }
    uart1->ctrl |= RIEN_MASK;
  }
}

int TrainTxInit() {
  // init UART1
  uart *const uart1 = (uart*)( UART1_BASE );
  uart1->lin_ctrl_mid = 0x0;
  uart1->lin_ctrl_low = 0xbf;
  uart1->lin_ctrl_high &= ~FEN_MASK;
  uart1->ctrl |= MSIEN_MASK;

  vic *const vic2 = (vic*)( VIC2_BASE );

  // init UART1 handler
  vic2->vect_addr2 = (int)( &Uart1Handler ) + CODE_BASE;
  vic2->vect_cntl2 = 0x34;

  // init UAR12INTR
  vic2->int_select = 0;
  vic2->int_enable |= 0x100000;
  return SUCCESS;
}

void TrainTxNotifier() {
    
  iobuf buf;
  IobufInit(&buf);
  
  int event;
  int comserver_tid = WhoIs("train-com-server");
  
  for ( ; ; ) {
    // determine type of interrupt
    AwaitEvent( EVENT_UART1_TX, (char*)&event, sizeof(int) );    
    Send(comserver_tid, NULL, 0, NULL, 0);
  }
  Exit();
}

void TrainTransmitter() {

  iobuf buf;
  IobufInit(&buf);
  
  train_tx_reply reply;
  
  uart *const uart1 = (uart*)( UART1_BASE );  

  RegisterAs("txmitter");
  int server_tid = WhoIs("train-tx-server");
  int comserver_tid = WhoIs("train-com-server");
  
  int c;      

  for (;;) {     
    if (!IobufEmpty(&buf)) {     
      Send(comserver_tid, NULL, 0, NULL, 0);   
      Send(comserver_tid, NULL, 0, NULL, 0);
        
      IobufDequeue( &buf, &c );
      uart1->data = c;
    } else {            
      // request more characters
      Send(server_tid, NULL, 0, (char*)&reply, sizeof(train_tx_reply));            
      
      int i;
      for (i = 0; i < reply.len; i++) {
        IobufEnqueue( &buf, reply.buf[i] );
      }
    }
  }
  Exit();
}

void ComServer() {
  
  train_tx_req msg;
  train_tx_reply reply;

  int sender_tid;
  int new_state;
  int state = TXREADY;

  RegisterAs("train-com-server");

  int txmitter_tid = WhoIs("txmitter");
  int notifier_tid = Create( MAX_PRI-1, &TrainTxNotifier );
  
  uart *const uart1 = (uart*)( UART1_BASE );
  
  for ( ; ; ) {
    Receive( &sender_tid, (char*)&msg, sizeof(train_tx_req));
    
    if (sender_tid == notifier_tid) {    
      // MS interrupt occurred      
      Reply( notifier_tid, NULL, 0 ); // early reply
      
      if (uart1->flag & CTS_MASK) {
        new_state = TXREADY;
      } else {
        new_state = STALLED;
      } 
    
      if (state == STALLED && new_state == TXREADY) {
        state = new_state;
        reply.len = 0;
        Reply( txmitter_tid, (char*)&reply, sizeof(train_tx_reply) );
      }
      else {
        state = new_state;
      }
    } else {
      // transmitter asking for status
      if (state == TXREADY) {
        Reply( txmitter_tid, NULL, 0 );   
      }
    }
  }
  Exit();
}

void TrainTxServer() {
    
  TrainTxInit();

  iobuf buf;
  IobufInit(&buf);
  
  train_tx_reply reply;
  char msg;
  
  RegisterAs("train-tx-server");
  
  Create(8, &ComServer);
  
  int sender_tid;
  int txmitter_empty = TRUE;
  int txmitter_tid = Create(9, &TrainTransmitter);  
    
  for ( ; ; ) {
    Receive( &sender_tid, (char*)&msg, sizeof(train_tx_req));
    
    if (sender_tid == txmitter_tid) {
      // transmitter asking for more characters        
      if (!IobufEmpty(&buf)) {
        int i;
        reply.len = (IobufSize(&buf) < 16 ? IobufSize(&buf) : 16);
        for (i = 0; i < reply.len; i++) {
          IobufDequeue( &buf, &(reply.buf[i]) );
        }          
        
        txmitter_empty = FALSE;
        Reply( txmitter_tid, (char*)&reply, sizeof(train_tx_reply) );        
      } else {          
        txmitter_empty = TRUE;
      }      
    } else {
      // system call request received!
      
      IobufEnqueue(&buf, msg);
      Reply( sender_tid, NULL, 0 );
      
      if (txmitter_empty && !IobufEmpty(&buf)) {        
        int i;
        reply.len = (IobufSize(&buf) < 16 ? IobufSize(&buf) : 16);
        for (i = 0; i < reply.len; i++) {
          IobufDequeue( &buf, &(reply.buf[i]) );
        }    
        
        txmitter_empty = FALSE;
        Reply( txmitter_tid, (char*)&reply, sizeof(train_tx_reply) );     
      }
    }
  }
  Exit();
}

